﻿using DNFImagePacks2Manager.Helpers;
using System;
using System.Collections.Generic;
using System.IO;

namespace DNFImagePacks2Manager.NPK
{
    public class DDSData
    {

        

        public int Length { get; set; }
        public int Flags { get; set; }
        public int Width { get; set; } = 4;
        public int Height { get; set; } = 4;
        public int Count { set; get; } = 1;
        public List<DDSMipmap> mipmaps { get; private set; } = [];
        public int Pitch { get; internal set; }
        public int Depth { get; internal set; }
        public byte[] Reverse { set; get; } = new byte[11];
        public int PixelFormatSize { get; internal set; }

        private const int Dxt1 = 0x31545844;
        private const int Dxt3 = 0x33545844;
        private const int Dxt5 = 0x35545844;
        private const int DdsMagic = 0x20534444;
        private const int DdsMipmapCount = 0x20000;

        public DDSData(byte[] bytes)
        {
            using MemoryStream memoryStream = new(bytes);
            using BinaryReader reader = new(memoryStream);
            byte[] magicBytes = reader.ReadBytes(4);
            var magic = ByteHelper.Bytes2Int(magicBytes);
            if (magic != DdsMagic)
            {
                throw new Exception("Invalid magic number in DDS header");
            }
            Length = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            byte[] flagBytes = reader.ReadBytes(4);
            Flags = ByteHelper.Bytes2Int(flagBytes);
            Height = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            Width = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            Pitch = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            Depth = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            var count = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            Reverse = reader.ReadBytes(11);
            reader.BaseStream.Position += 37;
            var pfFlags = ByteHelper.Bytes2Int(reader.ReadBytes(4));
            if ((pfFlags & DdsMipmapCount) != 0)
            {
                Count = Math.Max(1, count);
            }
            byte[] formatBytes = reader.ReadBytes(4);
            var format = ByteHelper.Bytes2Int(formatBytes);
            var blockBytes = 0;
            switch (format)
            {
                case Dxt1:
                    blockBytes = 8;
                    break;
                case Dxt3:
                case Dxt5:
                    blockBytes = 16;
                    break;
            }
            var offset = Length + 4;
            reader.BaseStream.Position = offset;
            var width = Width;
            var height = Height;
            var len = width * height / 16 * blockBytes;
            for (var i = 0; i < Count; i++)
            {
                byte[] data = reader.ReadBytes(len);
                switch (format)
                {
                    case Dxt1:
                        data = DecodeDxt1(data, width, height);
                        break;
                    case Dxt3:
                        data = DecodeDxt3(data, width, height);
                        break;
                    case Dxt5:
                        data = DecodeDxt5(data, width, height);
                        break;
                }
                var mip = new DDSMipmap
                {
                    Width = width,
                    Height = height,
                    Data = data
                };
                mipmaps.Add(mip);
                width = Math.Max(width >> 1, 1);
                height = Math.Max(height >> 1, 1);
            }
        }

        public DDSMipmap? GetMipmap(int index = 0)
        {
            if(index < mipmaps.Count)
            {
                return mipmaps[index];
            }
            else
            {
                return null;
            }
        }

        public static byte[] DecodeDxt1(byte[] data, int width, int height)
        {
            using var reader = new BinaryReader(new MemoryStream(data));
            var buf = new byte[width * height * 4];

            for (var y = 0; y < height; y += 4)
            {
                for (var x = 0; x < width; x += 4)
                {
                    ushort co0 = reader.ReadUInt16();
                    ushort co1 = reader.ReadUInt16();

                    byte[][] colors = new byte[4][];
                    colors[0] = DecodeRgb565(co0);
                    colors[1] = DecodeRgb565(co1);
                    colors[2] = new byte[4];
                    colors[3] = new byte[4];

                    if (co0 > co1)
                    {
                        colors[2] = new byte[]
                        {
                        (byte)((colors[0][0] * 2 + colors[1][0]) / 3),
                        (byte)((colors[0][1] * 2 + colors[1][1]) / 3),
                        (byte)((colors[0][2] * 2 + colors[1][2]) / 3),
                        0xFF
                        };
                        colors[3] = new byte[]
                        {
                        (byte)((colors[0][0] + colors[1][0] * 2) / 3),
                        (byte)((colors[0][1] + colors[1][1] * 2) / 3),
                        (byte)((colors[0][2] + colors[1][2] * 2) / 3),
                        0xFF
                        };
                    }
                    else
                    {
                        colors[2] = new byte[]
                        {
                        (byte)((colors[0][0] + colors[1][0]) / 2),
                        (byte)((colors[0][1] + colors[1][1]) / 2),
                        (byte)((colors[0][2] + colors[1][2]) / 2),
                        0xFF
                        };
                        colors[3] = new byte[] { 0x00, 0x00, 0x00, 0x00 };
                    }

                    uint index = reader.ReadUInt32();
                    for (var i = 0; i < 16; i++, index >>= 2)
                    {
                        int colorIdx = (int)(index & 0x3);
                        int pixelX = x + (i % 4);
                        int pixelY = y + (i / 4);
                        int pos = 4 * (pixelY * width + pixelX);

                        buf[pos] = colors[colorIdx][0];
                        buf[pos + 1] = colors[colorIdx][1];
                        buf[pos + 2] = colors[colorIdx][2];
                        buf[pos + 3] = colors[colorIdx][3];
                    }
                }
            }

            return buf;
        }

        public static byte[] DecodeDxt3(byte[] data, int width, int height)
        {
            using var reader = new BinaryReader(new MemoryStream(data));
            var buf = new byte[width * height * 4];

            for (var y = 0; y < height; y += 4)
            {
                for (var x = 0; x < width; x += 4)
                {
                    ushort[] alphas = new ushort[4];
                    alphas[0] = reader.ReadUInt16();
                    alphas[1] = reader.ReadUInt16();
                    alphas[2] = reader.ReadUInt16();
                    alphas[3] = reader.ReadUInt16();

                    ushort c0 = reader.ReadUInt16();
                    ushort c1 = reader.ReadUInt16();

                    uint index = reader.ReadUInt32();

                    byte[] color0 = DecodeRgb565(c0);
                    byte[] color1 = DecodeRgb565(c1);
                    byte[] color2 = new byte[]
                    {
                    (byte)((color0[0] * 2 + color1[0]) / 3),
                    (byte)((color0[1] * 2 + color1[1]) / 3),
                    (byte)((color0[2] * 2 + color1[2]) / 3),
                    0xFF
                    };
                    byte[] color3 = new byte[]
                    {
                    (byte)((color0[0] + color1[0] * 2) / 3),
                    (byte)((color0[1] + color1[1] * 2) / 3),
                    (byte)((color0[2] + color1[2] * 2) / 3),
                    0xFF
                    };
                    byte[][] colors = new[] { color0, color1, color2, color3 };

                    for (var i = 0; i < 16; i++, index >>= 2)
                    {
                        int colorIdx = (int)(index & 0x3);
                        int alphaIdx = i / 4;
                        int pixelX = x + (i % 4);
                        int pixelY = y + (i / 4);
                        int pos = 4 * (pixelY * width + pixelX);

                        byte alpha4 = (byte)((alphas[alphaIdx] >> (12 - 4 * (i % 4))) & 0xF);
                        byte alpha8 = (byte)(alpha4 | (alpha4 << 4));

                        buf[pos] = colors[colorIdx][0];
                        buf[pos + 1] = colors[colorIdx][1];
                        buf[pos + 2] = colors[colorIdx][2];
                        buf[pos + 3] = alpha8;
                    }
                }
            }

            return buf;
        }

        public static byte[] DecodeDxt5(byte[] data, int width, int height)
        {
            using var reader = new BinaryReader(new MemoryStream(data));
            var buf = new byte[width * height * 4];

            for (var y = 0; y < height; y += 4)
            {
                for (var x = 0; x < width; x += 4)
                {
                    byte[] alphas = new byte[8];
                    alphas[0] = reader.ReadByte();
                    alphas[1] = reader.ReadByte();

                    if (alphas[0] > alphas[1])
                    {
                        alphas[2] = (byte)((6 * alphas[0] + 1 * alphas[1]) / 7);
                        alphas[3] = (byte)((5 * alphas[0] + 2 * alphas[1]) / 7);
                        alphas[4] = (byte)((4 * alphas[0] + 3 * alphas[1]) / 7);
                        alphas[5] = (byte)((3 * alphas[0] + 4 * alphas[1]) / 7);
                        alphas[6] = (byte)((2 * alphas[0] + 5 * alphas[1]) / 7);
                        alphas[7] = (byte)((1 * alphas[0] + 6 * alphas[1]) / 7);
                    }
                    else
                    {
                        alphas[2] = (byte)((4 * alphas[0] + 1 * alphas[1]) / 5);
                        alphas[3] = (byte)((3 * alphas[0] + 2 * alphas[1]) / 5);
                        alphas[4] = (byte)((2 * alphas[0] + 3 * alphas[1]) / 5);
                        alphas[5] = (byte)((1 * alphas[0] + 4 * alphas[1]) / 5);
                        alphas[6] = 0x00;
                        alphas[7] = 0xFF;
                    }

                    ulong alphaBit = Read6Byte(reader);

                    ushort c0 = reader.ReadUInt16();
                    ushort c1 = reader.ReadUInt16();
                    byte[][] colors = new byte[4][];
                    colors[0] = DecodeRgb565(c0);
                    colors[1] = DecodeRgb565(c1);
                    colors[2] = new byte[]
                    {
                (byte)((colors[0][0] * 2 + colors[1][0]) / 3),
                (byte)((colors[0][1] * 2 + colors[1][1]) / 3),
                (byte)((colors[0][2] * 2 + colors[1][2]) / 3),
                0xFF
                    };
                    colors[3] = new byte[]
                    {
                (byte)((colors[0][0] + colors[1][0] * 2) / 3),
                (byte)((colors[0][1] + colors[1][1] * 2) / 3),
                (byte)((colors[0][2] + colors[1][2] * 2) / 3),
                0xFF
                    };

                    uint colorIndex = reader.ReadUInt32();

                    for (var i = 0; i < 16; i++)
                    {
                        int cIdx = (int)(colorIndex & 0x3);
                        int aIdx = (int)((alphaBit >> (3 * i)) & 0x7);

                        int pixelX = x + (i % 4);
                        int pixelY = y + (i / 4);
                        int pos = 4 * (pixelY * width + pixelX);

                        buf[pos] = colors[cIdx][0];
                        buf[pos + 1] = colors[cIdx][1];
                        buf[pos + 2] = colors[cIdx][2];
                        buf[pos + 3] = alphas[aIdx];

                        colorIndex >>= 2;
                    }
                }
            }

            return buf;
        }

        public static byte[] DecodeRgb565(ushort color)
        {
            byte r = (byte)(color & 0x1F);
            byte g = (byte)((color >> 5) & 0x3F);
            byte b = (byte)((color >> 11) & 0x1F);
            byte a = 0xFF;

            r = (byte)((r << 3) | (r >> 2));
            g = (byte)((g << 2) | (g >> 4));
            b = (byte)((b << 3) | (b >> 2));

            return new[] { r, g, b, a };
        }

        private static ulong Read6Byte(BinaryReader reader)
        {
            byte[] buf = reader.ReadBytes(6);
            byte[] extendedBuf = new byte[8];
            Array.Copy(buf, extendedBuf, 6);
            return BitConverter.ToUInt64(extendedBuf, 0);
        }

    }
}
